package org.xx.armory.db.impl;


import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;

import static org.xx.armory.commons.Validators.notNull;

/**
 * 数据库连接代理，使用底层的数据库连接实现功能。
 */
final class SimpleConnectionProxy
        implements Connection {
    private final SimpleDataSource owner;
    private boolean free;
    private Connection innerConnection;

    /**
     * 构造数据库连接代理的实例。
     *
     * @param owner
     *         持有此代理实例的数据源。
     * @throws IllegalArgumentException
     *         如果参数{@code owner}是{@code null}。
     */
    SimpleConnectionProxy(
            SimpleDataSource owner
    ) {
        this.owner = notNull(owner, "owner");
        this.free = true;
        this.innerConnection = null;
    }

    /**
     * 测试底层连接是否可用。
     * <p>如果底层连接不可用，那么创建一个可用的新连接。</p>
     *
     * @throws SQLException
     *         如果测试数据库连接可用性出错或者创建新的可用连接输错。
     */
    final void test()
            throws SQLException {
        if (this.innerConnection == null) {
            this.innerConnection = createInnerConnection();
        } else if (!this.innerConnection.isValid(0)) {
            try {
                this.innerConnection.close();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            this.innerConnection = createInnerConnection();
        }
    }

    /**
     * 创建底层连接。
     *
     * @return 已创建的底层连接。
     * @throws SQLException
     *         如果创建底层连接出错。
     */
    final Connection createInnerConnection()
            throws SQLException {
        if (!owner.getUsername().isEmpty()) {
            return DriverManager.getConnection(owner.getUrl(), owner.getUsername(), owner.getPassword());
        } else {
            return DriverManager.getConnection(owner.getUrl());
        }
    }

    /**
     * 判断当前连接代理是否处于空闲状态。
     *
     * @return 如果处于空闲状态则返回{@code true}，否则返回{@code false}。
     */
    boolean isFree() {
        return this.free;
    }

    /**
     * 设置当前连接是否处于空闲状态。
     *
     * @param free
     *         当前连接是否处于空闲状态。
     */
    void setFree(boolean free) {
        this.free = free;
    }

    /**
     * 关闭代理，只有数据源被关闭时才应当执行此方法。
     */
    void shutdown() {
        if (this.innerConnection != null) {
            try {
                this.innerConnection.close();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            this.innerConnection = null;
        }
        this.free = true;
    }

    /**
     * 获取底层数据库连接。
     *
     * @return 底层数据库连接。
     * @throws IllegalStateException
     *         如果当前的底层数据库连接是{@code null}。
     */
    private Connection getInner() {
        if (this.innerConnection == null) {
            throw new IllegalStateException("inner connection is null");
        }
        return this.innerConnection;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Statement createStatement()
            throws SQLException {
        return getInner().createStatement();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PreparedStatement prepareStatement(
            String sql
    )
            throws SQLException {
        return getInner().prepareStatement(sql);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public CallableStatement prepareCall(
            String sql
    )
            throws SQLException {
        return getInner().prepareCall(sql);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String nativeSQL(
            String sql
    )
            throws SQLException {
        return getInner().nativeSQL(sql);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setAutoCommit(
            boolean autoCommit
    )
            throws SQLException {
        getInner().setAutoCommit(autoCommit);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setTypeMap(
            Map<String, Class<?>> map
    )
            throws SQLException {
        getInner().setTypeMap(map);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setHoldability(
            int holdability
    )
            throws SQLException {
        getInner().setHoldability(holdability);
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public boolean getAutoCommit()
            throws SQLException {
        return getInner().getAutoCommit();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void commit()
            throws SQLException {
        getInner().commit();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void rollback()
            throws SQLException {
        getInner().rollback();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void close()
            throws SQLException {
        this.owner.restoreConnection(this);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isClosed()
            throws SQLException {
        // 连接代理处于空闲状态，就看作已被关闭。
        return this.isFree();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public DatabaseMetaData getMetaData()
            throws SQLException {
        return getInner().getMetaData();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setReadOnly(boolean readOnly)
            throws SQLException {
        getInner().setReadOnly(readOnly);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isReadOnly()
            throws SQLException {
        return getInner().isReadOnly();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setCatalog(String catalog)
            throws SQLException {
        getInner().setCatalog(catalog);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getCatalog()
            throws SQLException {
        return getInner().getCatalog();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setTransactionIsolation(int level)
            throws SQLException {
        getInner().setTransactionIsolation(level);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getTransactionIsolation()
            throws SQLException {
        return getInner().getTransactionIsolation();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public SQLWarning getWarnings()
            throws SQLException {
        return getInner().getWarnings();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void clearWarnings()
            throws SQLException {
        getInner().clearWarnings();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Statement createStatement(
            int resultSetType,
            int resultSetConcurrency
    )
            throws SQLException {
        return getInner().createStatement(resultSetType, resultSetConcurrency);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PreparedStatement prepareStatement(
            String sql,
            int resultSetType,
            int resultSetConcurrency
    )
            throws SQLException {
        return getInner().prepareStatement(sql, resultSetType, resultSetConcurrency);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public CallableStatement prepareCall(
            String sql,
            int resultSetType,
            int resultSetConcurrency
    )
            throws SQLException {
        return getInner().prepareCall(sql, resultSetType, resultSetConcurrency);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Map<String, Class<?>> getTypeMap()
            throws SQLException {
        return getInner().getTypeMap();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Savepoint setSavepoint(
            String name
    )
            throws SQLException {
        return getInner().setSavepoint(name);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void rollback(
            Savepoint savepoint
    )
            throws SQLException {
        getInner().rollback(savepoint);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getHoldability()
            throws SQLException {
        return getInner().getHoldability();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Savepoint setSavepoint()
            throws SQLException {
        return getInner().setSavepoint();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void releaseSavepoint(
            Savepoint savepoint
    )
            throws SQLException {
        getInner().releaseSavepoint(savepoint);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> T unwrap(
            Class<T> iface
    )
            throws SQLException {
        return getInner().unwrap(iface);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isWrapperFor(
            Class<?> iface
    )
            throws SQLException {
        return getInner().isWrapperFor(iface);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Statement createStatement(
            int resultSetType,
            int resultSetConcurrency,
            int resultSetHoldability
    )
            throws SQLException {
        return getInner().createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PreparedStatement prepareStatement(
            String sql,
            int resultSetType,
            int resultSetConcurrency,
            int resultSetHoldability
    )
            throws SQLException {
        return getInner().prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public CallableStatement prepareCall(
            String sql,
            int resultSetType,
            int resultSetConcurrency,
            int resultSetHoldability
    )
            throws SQLException {
        return getInner().prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PreparedStatement prepareStatement(
            String sql,
            int autoGeneratedKeys
    )
            throws SQLException {
        return getInner().prepareStatement(sql, autoGeneratedKeys);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PreparedStatement prepareStatement(
            String sql,
            int[] columnIndexes
    )
            throws SQLException {
        return getInner().prepareStatement(sql, columnIndexes);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PreparedStatement prepareStatement(
            String sql,
            String[] columnNames
    )
            throws SQLException {
        return getInner().prepareStatement(sql, columnNames);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Clob createClob()
            throws SQLException {
        return getInner().createClob();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Blob createBlob()
            throws SQLException {
        return getInner().createBlob();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public NClob createNClob()
            throws SQLException {
        return getInner().createNClob();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public SQLXML createSQLXML()
            throws SQLException {
        return getInner().createSQLXML();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isValid(int timeout)
            throws SQLException {
        return getInner().isValid(timeout);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setClientInfo(
            String name,
            String value
    )
            throws SQLClientInfoException {
        getInner().setClientInfo(name, value);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setClientInfo(Properties properties)
            throws SQLClientInfoException {
        getInner().setClientInfo(properties);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getClientInfo(String name)
            throws SQLException {
        return getInner().getClientInfo(name);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Properties getClientInfo()
            throws SQLException {
        return getInner().getClientInfo();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Array createArrayOf(
            String typeName,
            Object[] elements
    )
            throws SQLException {
        return getInner().createArrayOf(typeName, elements);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Struct createStruct(
            String typeName,
            Object[] attributes
    )
            throws SQLException {
        return getInner().createStruct(typeName, attributes);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setSchema(String schema)
            throws SQLException {
        getInner().setSchema(schema);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getSchema()
            throws SQLException {
        return getInner().getSchema();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void abort(Executor executor)
            throws SQLException {
        getInner().abort(executor);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getNetworkTimeout()
            throws SQLException {
        return getInner().getNetworkTimeout();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setNetworkTimeout(
            Executor executor,
            int timeout
    )
            throws SQLException {
        getInner().setNetworkTimeout(executor, timeout);
    }

}
